/**
 * Provides support for contacting craigslist and querying for a given posting, index page, search results page,
 * or email-to-friend page.  Allows developer to construct a paramaterized query and then send using a
 * jsx3.net.Request instance.  Also provides support for contacting the CL RSS service, using Yahoo!pipes as
 * the cross-domain transport.
 * @author birdeau@yahoo.com
 */
jsx3.lang.Class.defineClass("gicx.Query", jsx3.net.Request, [], function(Query,Query_prototype) {

  var queryInc = 0;

  Query.DEFAULT_SCHEME = "http://";
  Query.DEFAULT_REGION = "sfbay";
  Query.DEFAULT_HOST = "craigslist.org";
  Query.DEFAULT_SUBREGION = "sfc";
  Query.DEFAULT_CATEGORY = "sss";
  Query.DEFAULT_PAGE_SIZE = 100;
  Query.DEFAULT_FRIEND_FORM = {postingID:"",action:"send_email",type:"",email:"",rcptAddress:""};
  Query.PIPES_SEARCH_ID = "Wr08WNL42xG8NqN5jknRlg";  //this is the unique id given to the pipe that I created for search CL for RSS feeds

  Query.ADD = "addTwo";     //by-owner, by-dealer
  Query.PIC = "hasPic";     //1
  Query.MIN = "minAsk";     //\d*
  Query.MAX = "maxAsk";     //\d*
  Query.TITLE = "srchType"; //T
  Query.QUERY = "query";    //[\s\S]*
  Query.START = "s";


  Query_prototype.init = function(strId) {
    this.jsxsuper(strId);
    this._params = {addTwo:null,hasPic:null,minAsk:null,maxAsk:null,srchType:null,query:null,s:0};
  };


  Query_prototype.getProxyPrepend = function(strId) {
    //my HTTP server proxies the request to avoid x-domain security errors.  This prepend tells my server to proxy the request
    return (!jsx3.util.strEmpty(gicx.APP.getEnv("proxy"))) ? gicx.APP.getEnv("proxy") : "";
  };


  //composites the URL appropriate for executing a search for posting
  Query_prototype.getFriendURL = function() {
    //resolve the query parameters
    var a = [];
    Query.DEFAULT_FRIEND_FORM.postingID = this.postingid;
    for(var p in Query.DEFAULT_FRIEND_FORM)
      a.push(p + "=" + Query.DEFAULT_FRIEND_FORM[p]);

    //resolve the url
    var strURL = this.getScheme(gicx.Query.DEFAULT_SCHEME) +
                 this.getRegion(gicx.Query.DEFAULT_REGION) + "." +
                 this.getHost(gicx.Query.DEFAULT_HOST) + "/email.friend?" +
                 a.join("&");

    //return
    jsx3.log("Emailing Friend: " + this.getProxyPrepend() + strURL);
    return this.getProxyPrepend() + strURL;
  };


  //composites the URL appropriate for executing a search for postings
  Query_prototype.getSearchURL = function() {
    //special case: user can't search in titles if no query string; force the equivalent redirect that cl would issue
    if(this.getParam(Query.TITLE) && jsx3.util.strEmpty(this.getParam(Query.QUERY)))
      this.removeParam(Query.TITLE);
    var strParams = this._getEncodedParameters();
    if(strParams) {
      var strURL = this.getScheme(gicx.Query.DEFAULT_SCHEME) +
                   this.getRegion(gicx.Query.DEFAULT_REGION) + "." +
                   this.getHost(gicx.Query.DEFAULT_HOST) + "/search/" +
                   this.getCategory(gicx.Query.DEFAULT_CATEGORY) +
                   ((this.getSubregion()) ? "/" + this.getSubregion() : "") +
                   (strParams ? "?" + strParams : "");
    } else {
      //queries with no parameters directly index the list server in a paginated model
      if(!this.indexquery)
        this.indexquery = 0;

      var strURL = this.getScheme(gicx.Query.DEFAULT_SCHEME) +
                   this.getRegion(gicx.Query.DEFAULT_REGION) + "." +
                   this.getHost(gicx.Query.DEFAULT_HOST) +
                   ((this.getSubregion()) ? "/" + this.getSubregion() : "") + "/" +
                   this.getCategory(gicx.Query.DEFAULT_CATEGORY) + "/";

      //if greater than '0', add the index to search on the list
      if(this.indexquery)
        strURL += "index" + this.indexquery + ".html";
    }
    jsx3.log("Search URL: " + this.getProxyPrepend() + strURL);

    return this.getProxyPrepend() + strURL;
  };


  //composites the URL needed to return a specific posting
  Query_prototype.getPageURL = function() {
    var strURL = this.url;
    jsx3.log("Opening Posting: " + this.getProxyPrepend() + strURL);
    return this.getProxyPrepend() + strURL;
  };


  Query_prototype.getScheme = function(strDefault) {
    return this.scheme || strDefault;
  };

  Query_prototype.setScheme = function(strScheme) {
    this.scheme = strScheme;
  };

  Query_prototype.getRegion = function(strDefault) {
    return this.region || strDefault;
  };

  Query_prototype.setRegion = function(strRegion) {
    this.region = strRegion;
  };

  Query_prototype.getHost = function(strDefault) {
    return this.host || strDefault;
  };

  Query_prototype.setHost = function(strHost) {
    this.host = strHost;
  };

  Query_prototype.getSubregion = function() {
    return this.subregion;
  };

  Query_prototype.setSubregion = function(strSubregion) {
    this.subregion = strSubregion;
  };

  Query_prototype.getCategory = function(strDefault) {
    return this.category || strDefault;
  };

  Query_prototype.setCategory = function(strCategory) {
    this.category = strCategory;
  };

  Query_prototype.setURL = function(strURL) {
    this.url = strURL;
  };

  Query_prototype.getURL = function() {
    return this.url;
  };

  Query_prototype.setPostingId = function(intId) {
    this.postingid = intId;
  };

  Query_prototype.getPostingId = function() {
    return this.postingid;
  };

  Query_prototype.setParam = function(strName,strValue) {
    this._params[strName] = strValue;
  };

  Query_prototype.getParam = function(strName) {
    return this._params[strName];
  };

  Query_prototype.removeParam = function(strName) {
    delete this._params[strName];
  };

  Query_prototype.getParams = function(strCategory) {
    return this._params;
  };


  //returns URI-encoded parameter set for a given query
  Query_prototype._getEncodedParameters = function() {
    var a = [];
    for(var p in this._params)
      if(!(this._params[p] == null || (p == Query.START && this._params[p] == 0)))
        a.push(p + "=" + window.encodeURIComponent(this._params[p]));
    return a.join("&");
  };


  //method that wraps the open methosd on the super; emails the posting to a given email address
  Query_prototype.openForFriend = function(bAsync,strUserName,strUserPass) {
    //TO DO: use arguments object to generically persist these
    this._user = strUserName;
    this._pass = strUserPass;
    this._async = bAsync;
    this.open("get",this.getFriendURL(),bAsync,strUserName,strUserPass);
  };


  //method that wraps the open method on the super; gets a list of search results
  Query_prototype.openForSearch = function(bAsync,strUserName,strUserPass) {
    //TO DO: use arguments object to generically persist these
    this._user = strUserName;
    this._pass = strUserPass;
    this._async = bAsync;
    this.open("get",this.getSearchURL(),bAsync,strUserName,strUserPass);
  };


  Query_prototype.openForNext = function() {
    if(this.hasNext()) {
      if(!isNaN(this.indexquery)) {
        this.indexquery = this.indexquery + 100;
      } else {
        this.setParam(Query.START,this.getParam(Query.START) + Query.DEFAULT_PAGE_SIZE);
      }
      this.openForSearch(this._async,this._user,this._pass);
      return true;
    }
    return false;
  };


  Query_prototype.openForPrevious = function() {
    if(this.hasPrevious()) {
      if(!isNaN(this.indexquery)) {
        this.indexquery = this.indexquery - 100;
      } else {
        this.setParam(Query.START,this.getParam(Query.START) - Query.DEFAULT_PAGE_SIZE);
      }
      this.openForSearch(this._async,this._user,this._pass);
      return true;
    }
    return false;
  };


  //method that wraps the open method on the super; gets the specific search results page
  Query_prototype.openForPage = function(bAsync,strUserName,strUserPass) {
    var strPageUrl = this.getPageURL();
    if(!jsx3.util.strEmpty(strPageUrl)) {
      this._user = strUserName;
      this._pass = strUserPass;
      this._async = bAsync;
      this.open("get",strPageUrl,bAsync,strUserName,strUserPass);
      return true;
    }
    return false;
  };


  /**
   * Returns true if there are additional, paginated records to return AFTER the paginated set currently being vieweed
   * @returns {Boolean}
   */
  Query_prototype.hasNext = function() {
    if(!isNaN(this.indexquery))
      return true;
    var objRange = this.getRange();
    return objRange.end < objRange.total;
  };


  /**
   * Returns true if there are additional, paginated records to return PRIOR to the paginated set currently being viewed
   * @returns {Boolean}
   */
  Query_prototype.hasPrevious = function() {
    if(!isNaN(this.indexquery) && this.indexquery >= 100)
      return true;
    return this.getRange().start > 1
  };


  Query_prototype.getRange = function() {
    return (new gicx.Filter(this.getResponseText())).getRange();
  }


  /**
   * Returns the query profile, serialized as a CDF Record. This record can then be used to restore the query at a later time.
   * NOTE: do not serialize a query until it has run.
   * @param bSent {Boolean} pass false if the message has not been sent (don't feel like using a try catch)
   * @returns {jsx3.xml.Entity} CDF Record object
   */
  Query_prototype.serialize = function(bSent) {
    //create the base profile object
    var objParams = {scheme:this.getScheme(Query.DEFAULT_SCHEME),region:this.getRegion(Query.DEFAULT_REGION),host:this.getHost(Query.DEFAULT_HOST),category:this.getCategory(Query.DEFAULT_CATEGORY)};

    //add optional information
    if(this.subregion)
      objParams.subregion = this.subregion;

    //clone relevant query parameters; never save the page index
    for(var p in this._params)
      if(this._params[p])
        objParams[p] = this._params[p];
    delete objParams[Query.START];

    objParams.jsxid = (new Date()).valueOf();
    if(this.url)
      objParams.url = this.url;
    objParams.datetime = (new Date()).valueOf();

    var objDoc = jsx3.xml.CDF.Document.newDocument();
    return objDoc.insertRecord(objParams);
  };


  /**
   * Instances the query, by restoring a serialized instance
   * @param objRecord {jsx3.xml.Entity | strRecord} either an Entity or the record as string
   * @returns {gicx.Query}
   */
  Query.newFromRecord = function(objRecord) {
    //validate the input
    if(typeof(objRecord) == "string") {
      var objDoc = new jsx3.xml.CDF.Document.newDocument();
      objDoc.loadXML('<data jsxid="jsxroot">' + objRecord + '</data>');
      if(!objDoc.hasError()) {
        objRecord = objDoc.selectSingleNode("//record");
        if(!objRecord)
          throw "Invalid input type. Does not adhere to CDF format.";
      } else {
        throw "Invalid input type. Malformed XML.";
      }
    }
    if(!(objRecord instanceof jsx3.xml.Entity && objRecord.getNodeName() == "record"))
      throw "Invalid input type. Either not an instance of jsx3.xml.Entity or nodeName is not 'record'.";

    //create the instance
    var objNew = new gicx.Query("qx" + queryInc++);
    var objAtts = objRecord.getAttributes();
    for(var i=0;i<objAtts.size();i++) {
      var objAtt = objAtts.get(i);
      var strName = objAtt.getNodeName();
      var strValue = objAtt.getValue();
      if(strName == "url" || strName == "scheme" || strName == "region" || strName == "subregion" || strName == "host" || strName == "category") {
        objNew[strName] = strValue;
      } else if(strName != "jsxid" && strName != "count") {
        objNew.setParam(strName,strValue);
      }
    }
    return objNew;
  };


  /**
   * Sends the query via Yahoo pipes.  Enables safe x-domain searching without proxying on my host server.  Not as good as screen-scraping
   * @param objRecord {jsx3.xml.Entity | strRecord} either an Entity or the record as string
   * @returns {gicx.Query}
   */
  Query_prototype.sendWithPipes = function() {
    //create a new cb handler
    var strFnName = "cb" + ((new Date()).valueOf()) + queryInc++;
    this.pipes = true;
    var my = this;
    window[strFnName] = function(objJSON) {
      //modify the query object so that calling getREsponseText returns the JSON-formatted RSS feed
      my.getResponseText = function() { return objJSON; }
      my.publish({subject:jsx3.net.Request.EVENT_ON_RESPONSE});
      //dereference the dynamically created CB function now that no longer needed
      window[strFnName] = null;
    }

    //insert a script tag into the DOM; the code that yahoo pipes returns will call the dynamically-created cb which will call then call the registered client cb and finally unload (destroy) itself
    var strURL = "http://pipes.yahoo.com/pipes/pipe.run?clURL=" + window.encodeURIComponent(this.getSearchURL() + "&format=rss") + "&_id=" + Query.PIPES_SEARCH_ID + "&_run=1&_render=json&_callback=" + strFnName;
    gicx.APP.loadInclude(strURL, "script" + ((new Date()).valueOf()), "script", false);
  };

});




